gtkmenuprivate.h \
gtkmenuitemprivate.h \
gtkmenushellprivate.h \
+ gtkmenutracker.h \
gtkmnemonichash.h \
gtkmodelmenuitem.h \
gtkmodifierstyle.h \
gtkmenubutton.c \
gtkmenuitem.c \
gtkmenushell.c \
+ gtkmenutracker.c \
gtkmenutoolbutton.c \
gtkmessagedialog.c \
gtkmisc.c \
gtkmnemonichash.c \
- gtkmodelmenu.c \
gtkmodelmenuitem.c \
gtkmodifierstyle.c \
gtkmodules.c \
#include "gtkmain.h"
#include "gtkintl.h"
#include "gtktypebuiltins.h"
+#include "gtkmodelmenuitem.h"
#include "deprecated/gtktearoffmenuitem.h"
static void
gtk_menu_shell_dispose (GObject *object)
{
- gtk_menu_shell_deactivate (GTK_MENU_SHELL (object));
+ GtkMenuShell *menu_shell = GTK_MENU_SHELL (object);
+
+ g_clear_pointer (&menu_shell->priv->tracker, gtk_menu_tracker_free);
+ gtk_menu_shell_deactivate (menu_shell);
G_OBJECT_CLASS (gtk_menu_shell_parent_class)->dispose (object);
}
return menu_shell->priv->parent_menu_shell;
}
+
+static void
+gtk_menu_shell_tracker_insert_func (gint position,
+ GMenuModel *model,
+ gint item_index,
+ const gchar *action_namespace,
+ gboolean is_separator,
+ gpointer user_data)
+{
+ GtkMenuShell *menu_shell = user_data;
+ GtkWidget *item;
+
+ if (is_separator)
+ {
+ gchar *label;
+
+ item = gtk_separator_menu_item_new ();
+
+ if (g_menu_model_get_item_attribute (model, item_index, G_MENU_ATTRIBUTE_LABEL, "s", &label))
+ {
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item), label);
+ g_free (label);
+ }
+ }
+ else
+ item = gtk_model_menu_item_new (model, item_index, action_namespace);
+
+ gtk_menu_shell_insert (menu_shell, item, position);
+ gtk_widget_show (item);
+}
+
+static void
+gtk_menu_shell_tracker_remove_func (gint position,
+ gpointer user_data)
+{
+ GtkMenuShell *menu_shell = user_data;
+ GtkWidget *child;
+
+ child = g_list_nth_data (menu_shell->priv->children, position);
+ /* We use destroy here because in the case of an item with a submenu,
+ * the attached-to from the submenu holds a ref on the item and a
+ * simple gtk_container_remove() isn't good enough to break that.
+ */
+ gtk_widget_destroy (child);
+}
+
+/**
+ * gtk_menu_shell_bind_model:
+ * @menu_shell: a #GtkMenuShell
+ * @model: (allow-none): the #GMenuModel to bind to or %NULL to remove
+ * binding
+ * @action_namespace: (allow-none): the namespace for actions in @model
+ * @with_separators: %TRUE if toplevel items in @shell should have
+ * separators between them
+ *
+ * Establishes a binding between a #GtkMenuShell and a #GMenuModel.
+ *
+ * The contents of @shell are removed and then refilled with menu items
+ * according to @model. When @model changes, @shell is updated.
+ * Calling this function twice on @shell with different @model will
+ * cause the first binding to be replaced with a binding to the new
+ * model. If @model is %NULL then any previous binding is undone and
+ * all children are removed.
+ *
+ * @with_separators determines if toplevel items (eg: sections) have
+ * separators inserted between them. This is typically desired for
+ * menus but doesn't make sense for menubars.
+ *
+ * If @action_namespace is non-%NULL then the effect is as if all
+ * actions mentioned in the @model have their names prefixed with the
+ * namespace, plus a dot. For example, if the action "quit" is
+ * mentioned and @action_namespace is "app" then the effective action
+ * name is "app.quit".
+ *
+ * For most cases you are probably better off using
+ * gtk_menu_new_from_model() or gtk_menu_bar_new_from_model() or just
+ * directly passing the #GMenuModel to gtk_application_set_app_menu() or
+ * gtk_application_set_menu_bar().
+ *
+ * Since: 3.6
+ */
+void
+gtk_menu_shell_bind_model (GtkMenuShell *menu_shell,
+ GMenuModel *model,
+ const gchar *action_namespace,
+ gboolean with_separators)
+{
+ g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
+ g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
+
+ g_clear_pointer (&menu_shell->priv->tracker, gtk_menu_tracker_free);
+
+ while (menu_shell->priv->children)
+ gtk_container_remove (GTK_CONTAINER (menu_shell), menu_shell->priv->children->data);
+
+ if (model)
+ menu_shell->priv->tracker = gtk_menu_tracker_new (model, with_separators, action_namespace,
+ gtk_menu_shell_tracker_insert_func,
+ gtk_menu_shell_tracker_remove_func,
+ menu_shell);
+}
#include <gtk/gtkmenushell.h>
#include <gtk/gtkmnemonichash.h>
#include <gtk/gtkkeyhash.h>
-
+#include <gtk/gtkmenutracker.h>
G_BEGIN_DECLS
GList *children;
GtkWidget *active_menu_item;
GtkWidget *parent_menu_shell;
+ GtkMenuTracker *tracker; // if bound to a GMenuModel
guint button;
guint32 activate_time;
--- /dev/null
+/*
+ * Copyright © 2013 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#include "gtkmenutracker.h"
+
+typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
+
+struct _GtkMenuTracker
+{
+ GtkMenuTrackerInsertFunc insert_func;
+ GtkMenuTrackerRemoveFunc remove_func;
+ gpointer user_data;
+
+ GtkMenuTrackerSection *toplevel;
+};
+
+struct _GtkMenuTrackerSection
+{
+ GMenuModel *model;
+ GSList *items;
+ gchar *action_namespace;
+
+ guint with_separators : 1;
+ guint has_separator : 1;
+
+ gulong handler;
+};
+
+static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
+ GMenuModel *model,
+ gboolean with_separators,
+ gint offset,
+ const gchar *action_namespace);
+static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section);
+
+static GtkMenuTrackerSection *
+gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
+ GMenuModel *model,
+ gint *offset)
+{
+ GSList *item;
+
+ if (section->has_separator)
+ (*offset)++;
+
+ if (section->model == model)
+ return section;
+
+ for (item = section->items; item; item = item->next)
+ {
+ GtkMenuTrackerSection *subsection = item->data;
+
+ if (subsection)
+ {
+ GtkMenuTrackerSection *found_section;
+
+ found_section = gtk_menu_tracker_section_find_model (subsection, model, offset);
+
+ if (found_section)
+ return found_section;
+ }
+ else
+ (*offset)++;
+ }
+
+ return FALSE;
+}
+
+/* this is responsible for syncing the showing of a separator for a
+ * single subsection (and its children).
+ *
+ * we only ever show separators if we have _actual_ children (ie: we do
+ * not show a separator if the section contains only empty child
+ * sections). it's difficult to determine this on-the-fly, so we have
+ * this separate function to come back later and figure it out.
+ *
+ * 'section' is that section.
+ *
+ * 'tracker' is passed in so that we can emit callbacks when we decide
+ * to add/remove separators.
+ *
+ * 'offset' is passed in so we know which position to emit in our
+ * callbacks. ie: if we add a separator right at the top of this
+ * section then we would emit it with this offset. deeper inside, we
+ * adjust accordingly.
+ *
+ * could_have_separator is true in two situations:
+ *
+ * - our parent section had with_separators defined and we are not the
+ * first section (ie: we should add a separator if we have content in
+ * order to divide us from the items above)
+ *
+ * - if we had a 'label' attribute set for this section
+ *
+ * parent_model and parent_index are passed in so that we can give them
+ * to the insertion callback so that it can see the label (and anything
+ * else that happens to be defined on the section).
+ *
+ * we iterate each item in ourselves. for subsections, we recursively
+ * run ourselves to sync separators. after we are done, we notice if we
+ * have any items in us or if we are completely empty and sync if our
+ * separator is shown or not.
+ */
+static gint
+gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
+ GtkMenuTracker *tracker,
+ gint offset,
+ gboolean could_have_separator,
+ GMenuModel *parent_model,
+ gint parent_index)
+{
+ gboolean should_have_separator;
+ gint n_items = 0;
+ GSList *item;
+ gint i = 0;
+
+ for (item = section->items; item; item = item->next)
+ {
+ GtkMenuTrackerSection *subsection = item->data;
+
+ if (subsection)
+ {
+ gboolean could_have_separator;
+
+ could_have_separator = (section->with_separators && i > 0) ||
+ g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL);
+
+ n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items,
+ could_have_separator, section->model, i);
+ }
+ else
+ n_items++;
+
+ i++;
+ }
+
+ should_have_separator = could_have_separator && n_items != 0;
+
+ if (should_have_separator > section->has_separator)
+ {
+ /* Add a separator */
+ (* tracker->insert_func) (offset, parent_model, parent_index, NULL, TRUE, tracker->user_data);
+ section->has_separator = TRUE;
+ }
+ else if (should_have_separator < section->has_separator)
+ {
+ /* Remove a separator */
+ (* tracker->remove_func) (offset, tracker->user_data);
+ section->has_separator = FALSE;
+ }
+
+ n_items += section->has_separator;
+
+ return n_items;
+}
+
+static gint
+gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section)
+{
+ GSList *item;
+ gint n_items;
+
+ if (section == NULL)
+ return 1;
+
+ n_items = 0;
+
+ if (section->has_separator)
+ n_items++;
+
+ for (item = section->items; item; item = item->next)
+ n_items += gtk_menu_tracker_section_measure (item->data);
+
+ return n_items;
+}
+
+static void
+gtk_menu_tracker_remove_items (GtkMenuTracker *tracker,
+ GSList **change_point,
+ gint offset,
+ gint n_items)
+{
+ gint i;
+
+ for (i = 0; i < n_items; i++)
+ {
+ GtkMenuTrackerSection *subsection;
+ gint n;
+
+ subsection = (*change_point)->data;
+ *change_point = g_slist_delete_link (*change_point, *change_point);
+
+ n = gtk_menu_tracker_section_measure (subsection);
+ gtk_menu_tracker_section_free (subsection);
+
+ while (n--)
+ (* tracker->remove_func) (offset, tracker->user_data);
+ }
+}
+
+static void
+gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
+ GtkMenuTrackerSection *section,
+ GSList **change_point,
+ gint offset,
+ GMenuModel *model,
+ gint position,
+ gint n_items)
+{
+ while (n_items--)
+ {
+ GMenuModel *submenu;
+
+ submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION);
+ g_assert (submenu != model);
+ if (submenu != NULL)
+ {
+ GtkMenuTrackerSection *subsection;
+ gchar *action_namespace = NULL;
+
+ g_menu_model_get_item_attribute (model, position + n_items,
+ G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace);
+
+ if (section->action_namespace)
+ {
+ gchar *namespace;
+
+ namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL);
+ subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace);
+ g_free (namespace);
+ }
+ else
+ subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace);
+
+ *change_point = g_slist_prepend (*change_point, subsection);
+ g_free (action_namespace);
+ g_object_unref (submenu);
+ }
+ else
+ {
+ (* tracker->insert_func) (offset, model, position + n_items,
+ section->action_namespace, FALSE, tracker->user_data);
+ *change_point = g_slist_prepend (*change_point, NULL);
+ }
+ }
+}
+
+static void
+gtk_menu_tracker_model_changed (GMenuModel *model,
+ gint position,
+ gint removed,
+ gint added,
+ gpointer user_data)
+{
+ GtkMenuTracker *tracker = user_data;
+ GtkMenuTrackerSection *section;
+ GSList **change_point;
+ gint offset = 0;
+ gint i;
+
+ /* First find which section the changed model corresponds to, and the
+ * position of that section within the overall menu.
+ */
+ section = gtk_menu_tracker_section_find_model (tracker->toplevel, model, &offset);
+
+ /* Next, seek through that section to the change point. This gives us
+ * the correct GSList** to make the change to and also finds the final
+ * offset at which we will make the changes (by measuring the number
+ * of items within each item of the section before the change point).
+ */
+ change_point = §ion->items;
+ for (i = 0; i < position; i++)
+ {
+ offset += gtk_menu_tracker_section_measure ((*change_point)->data);
+ change_point = &(*change_point)->next;
+ }
+
+ /* We remove items in order and add items in reverse order. This
+ * means that the offset used for all inserts and removes caused by a
+ * single change will be the same.
+ *
+ * This also has a performance advantage: GtkMenuShell stores the
+ * menu items in a linked list. In the case where we are creating a
+ * menu for the first time, adding the items in reverse order means
+ * that we only ever insert at index zero, prepending the list. This
+ * means that we can populate in O(n) time instead of O(n^2) that we
+ * would do by appending.
+ */
+ gtk_menu_tracker_remove_items (tracker, change_point, offset, removed);
+ gtk_menu_tracker_add_items (tracker, section, change_point, offset, model, position, added);
+
+ /* The offsets for insertion/removal of separators will be all over
+ * the place, however...
+ */
+ gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
+}
+
+static void
+gtk_menu_tracker_section_free (GtkMenuTrackerSection *section)
+{
+ if (section == NULL)
+ return;
+
+ g_signal_handler_disconnect (section->model, section->handler);
+ g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free);
+ g_free (section->action_namespace);
+ g_object_unref (section->model);
+ g_slice_free (GtkMenuTrackerSection, section);
+}
+
+static GtkMenuTrackerSection *
+gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
+ GMenuModel *model,
+ gboolean with_separators,
+ gint offset,
+ const gchar *action_namespace)
+{
+ GtkMenuTrackerSection *section;
+
+ section = g_slice_new0 (GtkMenuTrackerSection);
+ section->model = g_object_ref (model);
+ section->with_separators = with_separators;
+ section->action_namespace = g_strdup (action_namespace);
+
+ gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model));
+ section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker);
+
+ return section;
+}
+
+/*< private >
+ * gtk_menu_tracker_new:
+ * @model: the model to flatten
+ * @with_separators: if the toplevel should have separators (ie: TRUE
+ * for menus, FALSE for menubars)
+ * @action_namespace: the passed-in action namespace
+ * @insert_func: insert callback
+ * @remove_func: remove callback
+ * @user_data user data for callbacks
+ *
+ * Creates a GtkMenuTracker for @model, holding a ref on @model for as
+ * long as the tracker is alive.
+ *
+ * This flattens out the model, merging sections and inserting
+ * separators where appropriate. It monitors for changes and performs
+ * updates on the fly. It also handles action_namespace for subsections
+ * (but you will need to handle it yourself for submenus).
+ *
+ * When the tracker is first created, @insert_func will be called many
+ * times to populate the menu with the initial contents of @model
+ * (unless it is empty), before gtk_menu_tracker_new() returns. For
+ * this reason, the menu that is using the tracker ought to be empty
+ * when it creates the tracker.
+ *
+ * Future changes to @model will result in more calls to @insert_func
+ * and @remove_func.
+ *
+ * The position argument to both functions is the linear 0-based
+ * position in the menu at which the item in question should be inserted
+ * or removed.
+ *
+ * For @insert_func, @model and @item_index are used to get the
+ * information about the menu item to insert. @action_namespace is the
+ * action namespace that actions referred to from that item should place
+ * themselves in. Note that if the item is a submenu and the
+ * "action-namespace" attribute is defined on the item, it will _not_ be
+ * applied to the @action_namespace argument as it is meant for the
+ * items inside of the submenu, not the submenu item itself.
+ *
+ * @is_separator is set to %TRUE in case the item being added is a
+ * separator. @model and @item_index will still be meaningfully set in
+ * this case -- to the section menu item corresponding to the separator.
+ * This is useful if the section specifies a label, for example. If
+ * there is an "action-namespace" attribute on this menu item then it
+ * should be ignored by the consumer because #GtkMenuTracker has already
+ * handled it.
+ *
+ * When using #GtkMenuTracker there is no need to hold onto @model or
+ * monitor it for changes. The model will be unreffed when
+ * gtk_menu_tracker_free() is called.
+ */
+GtkMenuTracker *
+gtk_menu_tracker_new (GMenuModel *model,
+ gboolean with_separators,
+ const gchar *action_namespace,
+ GtkMenuTrackerInsertFunc insert_func,
+ GtkMenuTrackerRemoveFunc remove_func,
+ gpointer user_data)
+{
+ GtkMenuTracker *tracker;
+
+ tracker = g_slice_new (GtkMenuTracker);
+ tracker->insert_func = insert_func;
+ tracker->remove_func = remove_func;
+ tracker->user_data = user_data;
+
+ tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, 0, action_namespace);
+ gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
+
+ return tracker;
+}
+
+/*< private >
+ * gtk_menu_tracker_free:
+ * @tracker: a #GtkMenuTracker
+ *
+ * Frees the tracker, ...
+ */
+void
+gtk_menu_tracker_free (GtkMenuTracker *tracker)
+{
+ gtk_menu_tracker_section_free (tracker->toplevel);
+ g_slice_free (GtkMenuTracker, tracker);
+}
--- /dev/null
+/*
+ * Copyright © 2013 Canonical Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt@desrt.ca>
+ */
+
+#ifndef __GTK_MENU_TRACKER_H__
+#define __GTK_MENU_TRACKER_H__
+
+#include <gio/gio.h>
+
+typedef struct _GtkMenuTracker GtkMenuTracker;
+
+typedef void (* GtkMenuTrackerInsertFunc) (gint position,
+ GMenuModel *model,
+ gint item_index,
+ const gchar *action_namespace,
+ gboolean is_separator,
+ gpointer user_data);
+
+typedef void (* GtkMenuTrackerRemoveFunc) (gint position,
+ gpointer user_data);
+
+
+G_GNUC_INTERNAL
+GtkMenuTracker * gtk_menu_tracker_new (GMenuModel *model,
+ gboolean with_separators,
+ const gchar *action_namespace,
+ GtkMenuTrackerInsertFunc insert_func,
+ GtkMenuTrackerRemoveFunc remove_func,
+ gpointer user_data);
+
+G_GNUC_INTERNAL
+void gtk_menu_tracker_free (GtkMenuTracker *tracker);
+
+#endif /* __GTK_MENU_TRACKER_H__ */
+++ /dev/null
-/*
- * Copyright © 2011 Red Hat, Inc.
- * Copyright © 2011 Canonical Limited
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the licence, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- *
- * Author: Matthias Clasen <mclasen@redhat.com>
- * Ryan Lortie <desrt@desrt.ca>
- */
-
-#include "config.h"
-
-#include "gtkmenushell.h"
-#include "gtkmenubar.h"
-#include "gtkmenu.h"
-
-#include "gtkseparatormenuitem.h"
-#include "gtkmodelmenuitem.h"
-#include "gtkapplicationprivate.h"
-
-#define MODEL_MENU_WIDGET_DATA "gtk-model-menu-widget-data"
-
-typedef struct {
- GMenuModel *model;
- GtkMenuShell *shell;
- guint update_idle;
- GSList *connected;
- gboolean with_separators;
- gint n_items;
- gchar *action_namespace;
-} GtkModelMenuBinding;
-
-static void
-gtk_model_menu_binding_items_changed (GMenuModel *model,
- gint position,
- gint removed,
- gint added,
- gpointer user_data);
-static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
- GMenuModel *model,
- const gchar *action_namespace,
- gboolean with_separators);
-
-static void
-gtk_model_menu_binding_free (gpointer data)
-{
- GtkModelMenuBinding *binding = data;
-
- /* disconnect all existing signal handlers */
- while (binding->connected)
- {
- g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
- g_object_unref (binding->connected->data);
-
- binding->connected = g_slist_delete_link (binding->connected, binding->connected);
- }
-
- g_object_unref (binding->model);
- g_free (binding->action_namespace);
-
- g_slice_free (GtkModelMenuBinding, binding);
-}
-
-static void
-gtk_model_menu_binding_append_item (GtkModelMenuBinding *binding,
- GMenuModel *model,
- const gchar *action_namespace,
- gint item_index,
- gchar **heading)
-{
- GMenuModel *section;
-
- if ((section = g_menu_model_get_item_link (model, item_index, "section")))
- {
- gchar *section_namespace = NULL;
-
- g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
- g_menu_model_get_item_attribute (model, item_index, "action-namespace", "s", §ion_namespace);
-
- if (action_namespace)
- {
- gchar *namespace = g_strjoin (".", action_namespace, section_namespace, NULL);
- gtk_model_menu_binding_append_model (binding, section, namespace, FALSE);
- g_free (namespace);
- }
- else
- {
- gtk_model_menu_binding_append_model (binding, section, section_namespace, FALSE);
- }
-
- g_free (section_namespace);
- g_object_unref (section);
- }
- else
- {
- GtkMenuItem *item;
-
- item = gtk_model_menu_item_new (model, item_index, action_namespace);
- gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
- gtk_widget_show (GTK_WIDGET (item));
- binding->n_items++;
- }
-}
-
-static void
-gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
- GMenuModel *model,
- const gchar *action_namespace,
- gboolean with_separators)
-{
- gint n, i;
-
- g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
- binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
-
- /* Deciding if we should show a separator is a bit difficult. There
- * are two types of separators:
- *
- * - section headings (when sections have 'label' property)
- *
- * - normal separators automatically put between sections
- *
- * The easiest way to think about it is that a section usually has a
- * separator (or heading) immediately before it.
- *
- * There are three exceptions to this general rule:
- *
- * - empty sections don't get separators or headings
- *
- * - sections only get separators and headings at the toplevel of a
- * menu (ie: no separators on nested sections or in menubars)
- *
- * - the first section in the menu doesn't get a normal separator,
- * but it can get a header (if it's not empty)
- *
- * Unfortunately, we cannot simply check the size of the section in
- * order to determine if we should place a header: the section may
- * contain other sections that are themselves empty. Instead, we need
- * to append the section, and check if we ended up with any actual
- * content. If we did, then we need to insert before that content.
- * We use 'our_position' to keep track of this.
- */
-
- n = g_menu_model_get_n_items (model);
-
- for (i = 0; i < n; i++)
- {
- gint our_position = binding->n_items;
- gchar *heading = NULL;
-
- gtk_model_menu_binding_append_item (binding, model, action_namespace, i, &heading);
-
- if (with_separators && our_position < binding->n_items)
- {
- GtkWidget *separator = NULL;
-
- if (heading)
- {
- separator = gtk_menu_item_new_with_label (heading);
- gtk_widget_set_sensitive (separator, FALSE);
- }
- else if (our_position > 0)
- separator = gtk_separator_menu_item_new ();
-
- if (separator)
- {
- gtk_menu_shell_insert (binding->shell, separator, our_position);
- gtk_widget_show (separator);
- binding->n_items++;
- }
- }
-
- g_free (heading);
- }
-}
-
-static void
-gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
-{
- GList *children;
-
- /* remove current children */
- children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
- while (children)
- {
- gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
- children = g_list_delete_link (children, children);
- }
-
- binding->n_items = 0;
-
- /* add new items from the model */
- gtk_model_menu_binding_append_model (binding, binding->model, binding->action_namespace, binding->with_separators);
-}
-
-static gboolean
-gtk_model_menu_binding_handle_changes (gpointer user_data)
-{
- GtkModelMenuBinding *binding = user_data;
-
- /* disconnect all existing signal handlers */
- while (binding->connected)
- {
- g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
- g_object_unref (binding->connected->data);
-
- binding->connected = g_slist_delete_link (binding->connected, binding->connected);
- }
-
- gtk_model_menu_binding_populate (binding);
-
- binding->update_idle = 0;
-
- g_object_unref (binding->shell);
-
- return G_SOURCE_REMOVE;
-}
-
-static void
-gtk_model_menu_binding_items_changed (GMenuModel *model,
- gint position,
- gint removed,
- gint added,
- gpointer user_data)
-{
- GtkModelMenuBinding *binding = user_data;
-
- if (binding->update_idle == 0)
- {
- binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
- g_object_ref (binding->shell);
- }
-}
-
-/**
- * gtk_menu_shell_bind_model:
- * @menu_shell: a #GtkMenuShell
- * @model: (allow-none): the #GMenuModel to bind to or %NULL to remove
- * binding
- * @action_namespace: (allow-none): the namespace for actions in @model
- * @with_separators: %TRUE if toplevel items in @shell should have
- * separators between them
- *
- * Establishes a binding between a #GtkMenuShell and a #GMenuModel.
- *
- * The contents of @shell are removed and then refilled with menu items
- * according to @model. When @model changes, @shell is updated.
- * Calling this function twice on @shell with different @model will
- * cause the first binding to be replaced with a binding to the new
- * model. If @model is %NULL then any previous binding is undone and
- * all children are removed.
- *
- * @with_separators determines if toplevel items (eg: sections) have
- * separators inserted between them. This is typically desired for
- * menus but doesn't make sense for menubars.
- *
- * If @action_namespace is non-%NULL then the effect is as if all
- * actions mentioned in the @model have their names prefixed with the
- * namespace, plus a dot. For example, if the action "quit" is
- * mentioned and @action_namespace is "app" then the effective action
- * name is "app.quit".
- *
- * For most cases you are probably better off using
- * gtk_menu_new_from_model() or gtk_menu_bar_new_from_model() or just
- * directly passing the #GMenuModel to gtk_application_set_app_menu() or
- * gtk_application_set_menu_bar().
- *
- * Since: 3.6
- */
-void
-gtk_menu_shell_bind_model (GtkMenuShell *shell,
- GMenuModel *model,
- const gchar *action_namespace,
- gboolean with_separators)
-{
- g_return_if_fail (GTK_IS_MENU_SHELL (shell));
- g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
-
- if (model)
- {
- GtkModelMenuBinding *binding;
-
- binding = g_slice_new (GtkModelMenuBinding);
- binding->model = g_object_ref (model);
- binding->shell = shell;
- binding->update_idle = 0;
- binding->connected = NULL;
- binding->with_separators = with_separators;
- binding->action_namespace = g_strdup (action_namespace);
-
- g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
-
- gtk_model_menu_binding_populate (binding);
- }
-
- else
- {
- GList *children;
-
- /* break existing binding */
- g_object_set_data (G_OBJECT (shell), "gtk-model-menu-binding", NULL);
-
- /* remove all children */
- children = gtk_container_get_children (GTK_CONTAINER (shell));
- while (children)
- {
- gtk_container_remove (GTK_CONTAINER (shell), children->data);
- children = g_list_delete_link (children, children);
- }
- }
-}
0, 2, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
}
-GtkMenuItem *
+GtkWidget *
gtk_model_menu_item_new (GMenuModel *model,
gint item_index,
const gchar *action_namespace)
gtk_model_menu_item_setup (item, model, item_index, action_namespace);
- return GTK_MENU_ITEM (item);
+ return GTK_WIDGET (item);
}
GType gtk_model_menu_item_get_type (void) G_GNUC_CONST;
G_GNUC_INTERNAL
-GtkMenuItem * gtk_model_menu_item_new (GMenuModel *model,
+GtkWidget * gtk_model_menu_item_new (GMenuModel *model,
gint item_index,
const gchar *action_namespace);